#include <config.h>
#include <pic_operation.h>
#include <picfmt_manager.h>
#include <file.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <unistd.h>
#include <png.h>
#include <zlib.h>


#define MODE_RGB    3
#define MODE_ARGB   4

static int isPNGFormat(PT_FileMap ptFileMap);
static int GetPixelDatasFrmPNG(PT_FileMap ptFileMap, PT_PixelDatas ptPixelDatas);
static int FreePixelDatasForPNG(PT_PixelDatas ptPixelDatas);

static T_PicFileParser g_tPNGParser = {
    .name = "png",
    .isSupport = isPNGFormat,
    .GetPixelDatas = GetPixelDatasFrmPNG,
    .FreePixelDatas = FreePixelDatasForPNG,
};

/**********************************************************************
 * 函数名称： isPNGFormat
 * 功能描述： PNG模块是否支持该文件,即该文件是否为PNG文件
 * 输入参数： ptFileMap - 内含文件信息
 * 输出参数： 无
 * 返 回 值： 0 - 不支持, 1 - 支持
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 240723	     V1.0	  Libq	      创建
 ***********************************************************************/
static int isPNGFormat(PT_FileMap ptFileMap)
{
    /*Libpng provides a simple check to see if a file is a PNG file.
    To use it, pass in the first 1 to 8 bytes of the file to the function
    png_sig_cmp(), and it will return 0 (false) if the bytes match the
    corresponding bytes of the PNG signature, or nonzero (true) otherwise.
    Of course, the more bytes you pass in, the greater the accuracy of the
    prediction.

    If you are intending to keep the file pointer open for use in libpng,
    you must ensure you don't read more than 8 bytes from the beginning
    of the file, and you also have to make a call to png_set_sig_bytes()
    with the number of bytes you read from the beginning.  Libpng will
    then only check the bytes (if any) that your program didn't read.

    If you had previously opened the file and read any of the signature from
    the beginning in order to see if this was a PNG file, you need to let
    libpng know that there are some bytes missing from the start of the file.

    png_set_sig_bytes(ptPNG, number);
    */
    int iRet;
    char strBuff[8];

    memcpy(strBuff, ptFileMap->pucFileMapMem, 8);

    iRet = png_sig_cmp(strBuff, 0, 8);

    DBG_PRINTF("isPNGFormat %s=%d\n", ptFileMap->strFileName, iRet);

    if (iRet == 0)
        return 1;
    else
        return 0;
}


/**********************************************************************
 * 函数名称： CovertOneLine
 * 功能描述： 根据BPP进行像素数据转换
 * 输入参数： iWidth      - 像素个数
 *           iSrcBpp     - 源数据BPP
 *           iDstBpp     - 目的数据BPP
 *           pudSrcDatas - 源像素数据，注意 png, bmp, jpg 传入像素数据格式不同，暂时无法通用
 * 输出参数： pudDstDatas - 目的像素数据
 * 返 回 值： 0 - 成功, 其他值 - 失败
 * 修改日期        版本号     修改人          修改内容
 * -----------------------------------------------
 * 240723        V1.0     Libq          创建
 * 240831        V2.0     Libq          修改
 ***********************************************************************/
static int CovertOneLine(int iWidth, int iSrcBpp, int iDstBpp, unsigned char *pudSrcDatas, unsigned char *pudDstDatas)
{
    unsigned int dwRed;
	unsigned int dwGreen;
	unsigned int dwBlue;
	unsigned int dwColor;

	unsigned short *pwDstDatas16bpp = (unsigned short *)pudDstDatas;
	unsigned int   *pwDstDatas32bpp = (unsigned int *)pudDstDatas;

	int i;

    /*
        BPP格式转换说明: from 32/24 to 32/24/16
    */

	if (iSrcBpp != 24 && iSrcBpp != 32)
	{
		return -1;
	}

	if (iDstBpp == iSrcBpp)
	{
        if (iSrcBpp == 32){     // from 32 to 32: RGBA --> ARGB
	        unsigned int dwAlpha;
            for (i = 0; i < iWidth; i++){
                dwRed    = pudSrcDatas[i * iSrcBpp/8 + 0];
                dwGreen  = pudSrcDatas[i * iSrcBpp/8 + 1];
                dwBlue   = pudSrcDatas[i * iSrcBpp/8 + 2];
			    dwAlpha  = pudSrcDatas[i * iSrcBpp/8 + 3];
                dwColor = (dwAlpha << 24) | (dwRed << 16) | (dwGreen << 8) | dwBlue;
				*pwDstDatas32bpp = dwColor;
				pwDstDatas32bpp++;
            }
        }
        else // from 24 to 24: RGb --> RGB
    		memcpy(pudDstDatas, pudSrcDatas, iWidth * iDstBpp / 8);
	}
	else // iDstBpp == iSrcBpp，不考虑alpha
	{
		for (i = 0; i < iWidth; i++)
		{
			dwRed   = pudSrcDatas[i * iSrcBpp/8 + 0];
			dwGreen = pudSrcDatas[i * iSrcBpp/8 + 1];
			dwBlue  = pudSrcDatas[i * iSrcBpp/8 + 2];
			if (iDstBpp == 32)
			{
				dwColor = (dwRed << 16) | (dwGreen << 8) | dwBlue;
				*pwDstDatas32bpp = dwColor;
				pwDstDatas32bpp++;
			}
			else if (iDstBpp == 24){
				*pudDstDatas = dwRed;
				pudDstDatas++;
				*pudDstDatas = dwGreen;
				pudDstDatas++;
				*pudDstDatas = dwBlue;
				pudDstDatas++;
			}
			else if (iDstBpp == 16)
			{
				/* 565 */
				dwRed   = dwRed >> 3;
				dwGreen = dwGreen >> 2;
				dwBlue  = dwBlue >> 3;
				dwColor = (dwRed << 11) | (dwGreen << 5) | (dwBlue);
				*pwDstDatas16bpp = dwColor;
				pwDstDatas16bpp++;
			}
		}
	}
    return 0;
}


/**********************************************************************
 * 函数名称： GetPixelDatasFrmPNG
 * 功能描述： 把PNG文件中的图像数据,取出并转换为能在显示设备上使用的格式
 * 输入参数： ptFileMap    - 内含文件信息
 * 输出参数： ptPixelDatas - 内含象素数据
 *            ptPixelDatas->iBpp 是输入的参数, 它确定从PNG文件得到的数据要转换为该BPP
 * 返 回 值： 0 - 成功, 其他值 - 失败
 * 修改日期        版本号     修改人          修改内容
 * -----------------------------------------------
 * 240723        V1.0     Libq          创建
 ***********************************************************************/
// 成功返回 0 ， 失败返回 非0    // 已经打开的png文件   // 返回给调用者的RGB图像数据
static int GetPixelDatasFrmPNG(PT_FileMap ptFileMap, PT_PixelDatas ptPixelDatas)
{
    png_structp ptPNG;
    png_infop ptInfo;
    png_uint_32 iWidth, iHeight;
    int iBitDepth, iColorType, iInterlaceType;
    int iRow;
    int iRGBMode = MODE_ARGB; // 3 for RGB, 4 for ARGB

    fseek(ptFileMap->tFp, 0, SEEK_SET); // 保证从文件头部开始读取

    // 创建 png 对象，使用 longjump 方法来捕获库函数中错误，后三个参数填 NULL
    ptPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (ptPNG == NULL)
    {
        DBG_PRINTF("png_create_read_struct error.\n");
        return -1;
    }
    /* Allocate/initialize the memory for image information.  REQUIRED. */
    ptInfo = png_create_info_struct(ptPNG);
    if (ptInfo == NULL)
    {
        png_destroy_read_struct(&ptPNG, NULL, NULL);
        DBG_PRINTF("png_create_info_struct error.\n");
        return -1;
    }

    // Set error handling if you are using the setjmp/longjmp method
    if (setjmp(png_jmpbuf(ptPNG))) // 使用 longjmp 方法
    {
        png_destroy_read_struct(&ptPNG, &ptInfo, NULL);
        DBG_PRINTF("png_jmpbuf error.\n");
        return -1;
    }

    png_init_io(ptPNG, ptFileMap->tFp);   // 初始化对象
    png_set_sig_bytes(ptPNG, 0);          // 说明已读取的字节数
    png_read_info(ptPNG, ptInfo);       // 读取 png 文件头中的信息
    png_get_IHDR(ptPNG, ptInfo, &iWidth, &iHeight, &iBitDepth, &iColorType,
                 &iInterlaceType, NULL, NULL); // 获取 png 文件头中的信息
    // DBG_PRINTF("\twidth=%d,iHeight=%d,iBitDepth=%d\n\tcolor_type=%d,iInterlaceType=%d\n",
    //            iWidth, iHeight, iBitDepth, iColorType, iInterlaceType);
    
    // 透明度处理
    if (iRGBMode == 3)
        png_set_strip_alpha(ptPNG);           // 丢弃透明度和背景
    else {
        if (iColorType!= PNG_COLOR_TYPE_RGBA) { // 保留透明度
            png_set_expand(ptPNG);
        }
        if (iBitDepth!= 8) {
            png_set_strip_16(ptPNG);
        }
        png_set_packing(ptPNG);
        if (iColorType == PNG_COLOR_TYPE_GRAY || iColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
            png_set_gray_to_rgb(ptPNG);
        }
    } // iRGBMode = 4;

    // if (iColorType == PNG_COLOR_TYPE_PALETTE)    // 如果是调色板颜色，转换成 RGB 颜色
    //     png_set_palette_to_rgb(ptPNG); 
    // png_set_invert_mono(ptPNG);                // 单色文件反转

    png_read_update_info(ptPNG, ptInfo); // 更新修改的配置
    // png_get_IHDR(ptPNG, ptInfo, &iWidth, &iHeight, &iBitDepth, &iColorType,
    //              &iInterlaceType, NULL, NULL); // 重新获取新配置

    // 准备数据输出给调用者
    ptPixelDatas->iWidth = iWidth;
    ptPixelDatas->iHeight = iHeight;
    // ptPixelDatas->iBpp          = 24; // 该变量被抽象类设置
    ptPixelDatas->iLineBytes = iWidth * (ptPixelDatas->iBpp >> 3);
    ptPixelDatas->iTotalBytes = iHeight * ptPixelDatas->iLineBytes;
    ptPixelDatas->aucPixelDatas = (unsigned char *)malloc(ptPixelDatas->iTotalBytes);

    if (!ptPixelDatas->aucPixelDatas)
    {
        DBG_PRINTF("%s malloc error!\n", ptFileMap->strFileName);
        return -1;
    }
    unsigned char *tmpbuf = (unsigned char *)malloc(iWidth * iHeight * iRGBMode);
    if (!tmpbuf)
    {
        free(ptPixelDatas->aucPixelDatas);
        return -1;
    }
    for (iRow = 0; iRow < iHeight; iRow++)
    {
        png_read_row(ptPNG, tmpbuf, NULL); // 读取一行数据，读取的数据为24BPP，RGB
        CovertOneLine(iWidth, iRGBMode * 8, ptPixelDatas->iBpp, tmpbuf,
                      ptPixelDatas->aucPixelDatas + iRow * ptPixelDatas->iLineBytes);
    }
    free(tmpbuf);
    png_read_end(ptPNG, ptInfo);                    // 读取结束调用 png_read_end
    png_destroy_read_struct(&ptPNG, &ptInfo, NULL); // 销毁对象
    return 0;                                           // 成功读取
}


/**********************************************************************
 * 函数名称： FreePixelDatasForPNG
 * 功能描述： GetPixelDatasFrmPNG的反函数,把象素数据所占内存释放掉
 * 输入参数： ptPixelDatas - 内含象素数据
 * 输出参数： 无
 * 返 回 值： 0 - 成功, 其他值 - 失败
 * 修改日期        版本号     修改人          修改内容
 * -----------------------------------------------
 * 240723        V1.0     Libq          创建
 ***********************************************************************/
static int FreePixelDatasForPNG(PT_PixelDatas ptPixelDatas)
{
    free(ptPixelDatas->aucPixelDatas);
    return 0;
}

/**********************************************************************
 * 函数名称： PNGParserInit
 * 功能描述： 注册"PNG文件解析模块"
 * 输入参数： 无
 * 输出参数： 无
 * 返 回 值： 0 - 成功, 其他值 - 失败
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 240723	     V1.0	  Libq	      创建
 ***********************************************************************/
int PNGParserInit(void)
{
    return RegisterPicFileParser(&g_tPNGParser);
}
